package org.dru.clay.rhino.util;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.dru.clay.util.functional.CollectionUtils;
import org.dru.clay.util.functional.Functions;
import org.dru.clay.util.functional.Predicate;
import org.mozilla.javascript.*;

import java.util.Arrays;
import java.util.Collection;

/**
 * RhinoUtils
 * User: joakimd
 * Date: 2013-07-22
 * Time: 16:18
 */
public final class RhinoUtils {
    private RhinoUtils() {
    }

    public static <T> T decode(Context context, Scriptable scope, Object value, Class<T> type) {
        Object json = NativeJSON.stringify(context, scope, value, null, null);
        Gson gson = new Gson();
        return gson.fromJson((String) json, type);
    }

    public static <T> T decode(Context context, Scriptable scope, Object value, TypeToken<T> type) {
        Object json = NativeJSON.stringify(context, scope, value, null, null);
        Gson gson = new Gson();
        return gson.fromJson((String) json, type.getType());
    }

    public static <T> Predicate<T> getPredicate(final Context context, final Scriptable thisObj, final Function predicate) {
        return new JavaScriptPredicate<T>(context, thisObj, predicate);
    }

    public static Scriptable fromCollection(final Context context, final Scriptable scope, final Collection<String> values) {
        return context.newArray(scope, values.toArray(new Object[values.size()]));
    }

    public static Collection<String> toCollection(final Scriptable array) {
        return CollectionUtils.transform(Arrays.asList(ScriptRuntime.getArrayElements(array)), Functions.getToString());
    }

    private static class JavaScriptPredicate<T> implements Predicate<T> {
        private final Context context;
        private final Scriptable thisObj;
        private final Function predicate;

        private JavaScriptPredicate(final Context context, final Scriptable thisObj, final Function predicate) {
            this.context = context;
            this.thisObj = thisObj;
            this.predicate = predicate;
        }

        @Override
        public boolean apply(final T value) {
            return Context.toBoolean(predicate.call(context, thisObj, predicate, new Object[]{value}));
        }
    }
}
